<?php
# ***** BEGIN LICENSE BLOCK *****
# This file is part of "myWiWall".
# Copyright (c) 2007-2008 CRP Henri Tudor and contributors.
# All rights reserved.
#
# "myWiWall" is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as published by
# the Free Software Foundation.
# 
# "myWiWall" is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with "myWiWall"; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# ***** END LICENSE BLOCK *****


/**
 * The Widgets model class provides an access to the persistant data relevant to Widgets
 * in MyWiWall.
 *
 * @author Laurent Haan <laurent.haan@gmail.com>
 * @author J�r�me Bogaerts <jerome.bogaerts@gmail.com>
 * @package mywiwall.models
 */
class Widgets
{
	/**
	 * Determines if a widget is installed in the mywiwall's widget repository.
	 *
	 * @param string $widgetId The widget's database identifier.
	 * @return bool Returns true if the widget is installed in the repository, false if not.
	 */
	public static function isInstalled($widgetId)
	{
		// Database connexion and parameters sanitization.
		$db = DbUtil::accessFactory();
		$widgetId = $db->escape($widgetId);
		
		// Is notified as installed in the widget repository ?
		$rs = $db->select("SELECT `widgetid` FROM `widgets` WHERE `widgetid` = '${widgetId}'");
			
		return $rs->count() == 1;
	}
	
	/**
	 * Uninstalls a given widget from the widget repository. All data relevant to the widgets
	 * (widget files and RDBMS) will be removed.
	 *
	 * @param string $widgetId The widget's database identifier of the widget you want to remove.
	 */
	public static function uninstall($widgetId)
	{		
		// Db connexion and parameters sanitization.
		$db = DbUtil::accessFactory();
		$widgetId = $db->escape($widgetId);
		
		// delete the widget from the widgets table.
		if (!$db->execute("DELETE FROM `widgets` WHERE `widgetid` = '${widgetId}'"))
		{
			throw new DBException(MwwException::MODEL, "Unable to delete the row with id '${widgetId}' in the widgets table");	
		}
		else
		{
			// delete the folder relevant to the deleted widget in the widgets repository.
			if (!Util::full_rmdir('./widgets/' . $widgetId))
				throw new FileException(MwwException::MODEL, "Unable to delete the folder containing the widget with id '${widgetId}'");
		}
	}
	
	/**
	 * Toggle the visibility of a widget (visible->invisible | invisible->visible).
	 * When a widget's visibility attribute is set to invisible, users will not be able
	 * to add it to their widget walls.
	 *
	 * @param string $widgetId The database identifier of the widget.
	 */
	public static function toggleVisibility($widgetId)
	{
		// Database connexion, params sanitization.
		$db = DbUtil::accessFactory();
		$widgetId = $db->escape($widgetId);
		
		// toggle the visibility of the widget in the database.
		if (!$db->execute("UPDATE `widgets` SET `visible` = !`visible` WHERE `widgetid` = '${widgetId}'"))
			throw new DBException(MwwException::MODEL, "Unable to set widget '${widgetId}' as visible/invisible in widgets table");
	}
	
	/**
	 * Retrieves the list of widgets a user may see/use.
	 *
	 * @param integer $userId The user id.
	 * @param string $userProfile The user profile.
	 * @param string $format The output format. Only 'json', 'xml' and 'raw' are allowed.
	 * @return mixed The widgets a user may see as 'json', 'xml' or 'raw' format.
	 */
	public static function retrieveWidgetList($userId, $userProfile = 'user', $format = 'json', $withAuth = false)
	{
		// Specified format handling.
		$format = strtolower($format);
		if ($format != 'json' && $format != 'xml' && $format != 'raw')
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByTag Model method only accepts json and xml as output format');
		
		// Given user profile verification.
		$userProfile = strtolower($userProfile);
		if (!self::verifyProfile($userProfile))
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByTag Model method only accepts user, admin and superadmin values for its userProfile parameter');
		
		// Database connexion retrieval.
		$db = DbUtil::accessFactory();
		$userId = $db->escape($userId);

		// Responses are different depending on the provided user profile.
		switch ($userProfile)
		{
			case 'superadmin':
				
				$rs = $db->select('SELECT wid.*, cat.*, cop.* FROM widgets wid LEFT OUTER JOIN categories cat ON (wid.category = cat.id) '
																				. 'LEFT JOIN cops cop ON (wid.copname = cop.copname) '
																				. 'ORDER BY cop.copname ASC, cat.category ASC, wid.widgetname ASC');
				break;
				
			case 'admin':
				
				$rs = $db->select("SELECT wid.*, cat.*, cop.* FROM widgets wid LEFT OUTER JOIN categories cat ON (wid.category = cat.id) "
																			    . "LEFT JOIN cops cop ON (wid.copname = cop.copname) "
																			    . "LEFT JOIN users us ON (cop.copname = us.copname AND us.id = ${userId}) "
																			    . "ORDER BY cat.category ASC, wid.widgetname ASC");
				break;
				
			case 'user':
			default:
				
				$rs = $db->select("SELECT wid.*, cat.*, cop.* FROM widgets wid LEFT OUTER JOIN categories cat ON (wid.category = cat.id) "
																				. "LEFT JOIN cops cop ON (wid.copname = cop.copname) "
																				. "LEFT JOIN users us ON (cop.copname = us.copname AND us.copname AND us.id = ${userId}) "
																				. "ORDER BY wid.widgetname ASC");
				break;
		}
		
			
		// We got the data, now we format it in an array.
		$widgets = array();
		while ($rs->fetch())
		{
			$authentication = array ();
			
			$authentication['use'] = ($rs->authkey) ? true : false;
			$authentication['key'] = $rs->authkey;
			
			if ($withAuth)
				$widgets[] = array('id' 			=> $rs->widgetid, 
								   'name' 			=> $rs->widgetname, 
								   'visible' 		=> $rs->visible, 
								   'category' 		=> $rs->category,
								   'authentication' => $authentication);
			else
				$widgets[] = array('id' 			=> $rs->widgetid, 
								   'name' 			=> $rs->widgetname, 
								   'visible' 		=> $rs->visible, 
								   'category' 		=> $rs->category);
		}
		
		return self::encodeWidgetListResponse($widgets, $format);
	}
	
	/**
	 * Retrieves the widgets that are associated with a particular tag that a user may
	 * see/use.
	 *
	 * @param integer $userId The user's identifier.
	 * @param string $tag The tag that define wich widgets will be found.
	 * @param string $userProfile The user profile.
	 * @param string $format 'json', 'xml' or raw format.
	 * @return mixed the widgets that are associated with a particular tag.
	 */
	public static function retrieveWidgetListByTag($userId, $tag, $userProfile = 'user', $format = 'json')
	{
		// Specified format handling.
		$format = strtolower($format);
		if ($format != 'json' && $format != 'xml' && $format != 'raw')
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByTag Model method only accepts json and xml as output format.');
		
		$userProfile = strtolower($userProfile);
		if (!self::verifyProfile($userProfile))
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByTag Model method only accepts user, admin and superadmin values for its userProfile parameter.');
		
		// Database connexion retrieval.
		$db = DbUtil::accessFactory();
		$userId = $db->escape($userId);
		$tag = $db->escape($tag);

		// Responses are different depending on the provided user profile.
		switch ($userProfile)
		{
			case 'superadmin':
				
				$rs = $db->select("SELECT `widgets`.`widgetid`, `widgets`.`widgetname`, `widgets`.`visible`, `widgets`.`category` FROM `widgets`, `widget_tags` WHERE `widget_tags`.`widget` = `widgets`.`widgetid` AND `widget_tags`.`tag` = '${tag}' ORDER BY `widgets`.`widgetname` ASC");
				break;
				
			case 'admin':
				
				$rs = $db->select("SELECT DISTINCT widgets.widgetid, widgets.widgetname, `widgets`.`visible`, `widgets`.`category` FROM widgets, users, widget_tags WHERE widget_tags.widget = widgets.widgetid AND widget_tags.tag = '${tag}' AND (`widgets`.`visible` = 1 OR (`widgets`.`visible` = 0 AND `widgets`.`copname` = `users`.`copname` AND `users`.`id`=  ${userId})) ORDER BY widgets.widgetname ASC");
				break;
				
			case 'user':
			default:
				
				$rs = $db->select("SELECT `widgets`.`widgetid`, `widgets`.`widgetname`, `widgets`.`visible`, `widgets`.`category` FROM `widgets`, `widget_tags` WHERE `widgets`.`visible` = 1 AND `widget_tags`.`widget` = `widgets`.`widgetid` AND `widget_tags`.`tag` = '${tag}' ORDER BY `widgets`.`widgetname` ASC");
				break;
		}
			
		// We got the data, now we format it in an array.
		$widgets = array();
		while($rs->fetch())
			$widgets[] = array('id' => $rs->widgetid, 'name' => $rs->widgetname, 'visible' => $rs->visible, 'category' => $rs->category);
			
		return self::encodeWidgetListResponse($widgets, $format);
	}
	
	/**
	 * Retrieves the widgets that are associated with a particular category that a user
	 * may see/use.
	 *
	 * @param integer $userId The user's identifier.
	 * @param string $category The category that define wich widgets will be found. If null is given, widgets without category will be returned.
	 * @param string $userProfile The user profile.
	 * @param string $format 'json', 'xml' or 'raw' format.
	 * @return mixed the widgets that are associated with a particular tag.
	 */
	public static function retrieveWidgetListByCategory($userId, $category, $userProfile = 'user', $format = 'json')
	{
		if (!$category)
			$category = 'NULL';
		
		// Specified format handling.
		$format = strtolower($format);
		if ($format != 'json' && $format != 'xml' && $format != 'raw')
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByCategory Model method only accepts json and xml as output format.');
		
		$userProfile = strtolower($userProfile);
		if (!self::verifyProfile($userProfile))
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::getWidgetListByTag Model method only accepts user, admin and superadmin values for its userProfile parameter.');
		
		// Database connexion retrieval.
		$db = DbUtil::accessFactory();
		$userId = $db->escape($userId);
		$category = $db->escape($category);
		
		$sqlParameter = ($category == 'NULL') ? 'IS NULL' : (' = ' . $category);

		// Responses are different depending on the provided user profile.
		switch ($userProfile)
		{
			case 'superadmin':
				$rs = $db->select("SELECT widgets.widgetid, widgets.widgetname, `widgets`.`visible`, `widgets`.`category` FROM widgets WHERE widgets.category ${sqlParameter} ORDER BY widgetname ASC");
				break;
				
			case 'admin':
				
				$rs = $db->select("SELECT DISTINCT widgets.widgetid, widgets.widgetname, `widgets`.`visible`, `widgets`.`category` FROM widgets, users WHERE widgets.category ${sqlParameter} AND (`widgets`.`visible` = 1 OR (`widgets`.`visible` = 0 AND `widgets`.`copname` = `users`.`copname` AND `users`.`id`= ${userId})) ORDER BY widgets.widgetname ASC");
				break;
				
			case 'user':
			default:
				$rs = $db->select("SELECT `widgets`.`widgetid`, `widgets`.`widgetname`, `widgets`.`visible`, `widgets`.`category` FROM `widgets` WHERE `widgets`.`visible` = 1 AND `widgets`.`category` ${sqlParameter} ORDER BY `widgets`.`widgetname` ASC");
				break;
		}
				
		// We got the data, now we format it in an array.
		$widgets = array();
		while($rs->fetch())
			$widgets[] = array('id' => $rs->widgetid, 'name' => $rs->widgetname, 'visible' => $rs->visible, 'category' => $rs->category);
			
		return self::encodeWidgetListResponse($widgets, $format);
	}
	
	/**
	 * Retrieve data as an associative array relevant to a particular widget.
	 * Accepted formats for the output are json or raw.
	 *
	 * @param string $widgetId The widget identifier.
	 * @param string $format The format you want to receive the Data. Accepted formats are 'json' or 'raw'.
	 * @return array|json The data relevant to the Widget.
	 */
	public static function retrieveWidget($widgetId, $format = 'json')
	{
		$format = strtolower($format);
		if ($format != 'json' && $format != 'raw')
		{
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::retrieveWidget Model method only accepts json and xml as output format.');	
		}
		else
		{
			// Database connexion and sanitization.
			$db = DbUtil::accessFactory();
			$widgetId = $db->escape($widgetId);
			
			$rs = $db->select("SELECT * FROM widgets WHERE widgetid = '${widgetId}'");
			
			if (!$rs->count() == 1)
			{
				throw new BadArgumentException(MwwException::MODEL, "Widget with id '${widgetId}' is unknown");	
			}
			else
			{
				$formattedData = array('id' 			=> $rs->widgetid, 
									   'name' 			=> $rs->widgetname, 
									   'visible' 		=> $rs->visible, 
									   'category' 		=> $rs->category);
				
				
				return ($format == 'json') ? json_encode($formattedData) : $formattedData;
			}
		}
	}
	
	/**
	 * Retrieve data relevant to a list of widget identifiers. Accepted output formats
	 * are JSON or raw.
	 *
	 * @param array $widgetIds A list of widget identifiers.
	 * @param string $format The format used for the output. Accepted formats are 'json' or 'raw'.
	 * @return array|json The data relevant to the widget identifier list.
	 */
	public static function retrieveWidgets(array $widgetIds, $format = 'json')
	{
		$format = strtolower($format);
		if ($format != 'json')
		{
			throw new BadArgumentException(MwwException::MODEL, 'The Widgets::retrieveWidgets Model method only accepts json as output format');	
		}
		else
		{
			$formattedData = array();
			foreach ($widgetIds as $widgetId)
				$formattedData[] = self::retrieveWidget($widgetId, 'raw');
				
			return json_encode($formattedData);
		}
	}
	
	
	/**
	 * Updates a widget (local or remote) installation providing it a eventual new category, and
	 * a new manifest/zip archive. The method will use the $uploadedFileName
	 * and $uploadedFilePath parameters to find out what they have to do.
	 *
	 * @param string $widgetId The widget's database identifier.
	 * @param integer $categoryId The widget's new category identifier.
	 * @param string $uploadedFileName The new file name that must be now used to replace this widget.
	 * @param string $uploadedFilePath The new absolute path of the file that must be now used to replace this widget.
	 * @return string Edited widget name.
	 */
	public static function edit($widgetId, $categoryId, $uploadedFileName = null, $uploadedFilePath = null)
	{
		// Database connexion retrieval and parameters sanitation.
		$db 		= DbUtil::accessFactory();
		$widgetId 	= $db->escape($widgetId);
		$categoryId = ($categoryId != 0) ? $db->escape($categoryId) : 'NULL';
		
		$widgetData = self::retrieveWidget($widgetId, 'raw');
		$widgetName = $widgetData['name'];
		
		// update the category.
		if (!$db->execute("UPDATE `widgets` SET `category`= ${categoryId} WHERE widgetid = '${widgetId}'"))
			throw new DBException(MwwException::MODEL, "Unable to edit category for widget '${widgetId}' in persistant database");	
			
		// If uploaded file information are provided, we modify the widget itself.
		if ($uploadedFileName && $uploadedFilePath)
		{		
			$type = Util::validateFileType($uploadedFileName);
			
			// test if the file is either an XML or zip archive.
			if (!$type)
				throw new FileException(MwwException::MODEL, "Unsupported file type, upload either an XML file or a ZIP archive for widget '${widgetId}' edition");
			
			
			if ($type == 'text/plain')
				$widgetName = self::editRemoteWidget($widgetId, $uploadedFilePath);
			else
				$widgetName = self::editLocalWidget($widgetId, $uploadedFilePath);	
		}
		
		return $widgetName;
	}
	
	/**
	 * Retrieve a widget XML manifest for a particular widget installed
	 * previously in the widget repository.
	 *
	 * @param string $widgetId The widget identifier.
	 * @return string The XML manifest as a character stream.
	 */
	public static function retrieveWidgetManifest($widgetId)
	{
		$doc = new DOMDocument();
		$doc->preserveWhiteSpace = false;
		$doc->load('widgets/' . $widgetId . '/config.xml');

		return $doc->saveXML();
	}
	
	/**
	 * Retrieve the authentication proof for a particular widget for the user
	 * currently logged on the system. It returns an associative array in JSON
	 * or php Array with the following keys :
	 * 
	 * - identifier: the username of the user currently logged on the system.
	 * - signature: the username encrypted using the generated key for this widget installation.
	 *
	 * @param string $widgetId The widget identifier.
	 * @param string $format The format of the output data. Accepted data are 'json' or 'raw'.
	 * @return array|json The identification proof for the relevant widget.
	 */
	public static function retrieveAuthenticationProof($widgetId, $format = 'json')
	{
		$format = strtolower($format);
		if ($format != 'json' && $format != 'raw')
			throw new BadArgumentException(MwwException::MODEL, 
										   'The retrieveAuthenticationProof model method accepts only json or raw as output format');
			
			
		$db = DbUtil::accessFactory();
		$widgetId = $db->escape($widgetId);
		
		$rs = $db->select("SELECT authkey FROM widgets WHERE widgetid = '${widgetId}'");
		if ($rs->count())
		{
			if ($rs->authkey != null)
			{
				$key 			= $rs->authkey;
				$username 		= Auth::getUserName();
				
				$crypto 		= new Rijndael();
				$signature		= $crypto->encrypt($username, $key);
				
				$proof = array('identifier' => $username,
							   'signature'	=> $signature);
				
				if ($format == 'json')
					return json_encode($proof);
				else
					return $proof;
			}
			else
			{
				throw new WidgetAuthenticationException(MwwException::MODEL, 
														"The widget with id '${widgetId}' is not authentication ready");
			}
		}
		else
		{
			throw new WidgetAuthenticationException(MwwException::MODEL, 
													"The widget with id '${widgetId}' does not exist");	
		}
	}
	
	/**
	 * Installs a new widget (local or remote) in mywiwall giving it a category.
	 * This method hopes to find the file at the location specified in the $uploadedFilePath 
	 * argument. It will determine if it is a remote or local widget, install it, and record
	 * all necessarry data in the database. Temporary files needed during the installation
	 * process are automatically cleaned up after installation.
	 *
	 * @param integer $categoryId The category in which the widget will be installed.
	 * @param integer $userId The identifier of the user who's installing the widget.
	 * @param string $uploadedFileName The new file name that must be now used to replace this widget.
	 * @param string $uploadedFilePath The new absolute path of the file that must be now used to replace this widget.
	 * @return string The database identifier of the widget which was installed. 
	 */
	public static function install($categoryId, $userId, $uploadedFileName, $uploadedFilePath)
	{
		// Get the uploaded file type.				
		$type = Util::validateFileType($uploadedFileName);
		
		// test if the file is either an XML or zip archive.
		if (!$type)
			throw new FileException(MwwException::MODEL, "Unsupported file type, upload either an XML file or a ZIP archive");
		
		// The file type is supported, it could be a simple manifest or a widget zip archive.
		if ($type == 'text/plain')
		{
			$widgetName = Widgets::installRemoteWidget($categoryId, $userId, $uploadedFilePath);
			return $widgetName;
		}
		else
		{
			$widgetName = Widgets::installLocalWidget($categoryId, $userId, $uploadedFilePath);
			return $widgetName;
		}
	}
	
	public function isUsingAuthentication($widgetId)
	{
		$db = DbUtil::accessFactory();
		$widgetId = $db->escape($widgetId);
		
		$rs = $db->select("SELECT authkey FROM widgets WHERE widgetid='${widgetId}'");
		
		return ($rs->authkey) ? true : false;
	}	
	
	private static function verifyProfile($userProfile)
	{
		// $levels comes from the APP/includes/config.php file.
		global $levels; // FIXME Please fixme that is just awful !
		foreach ($levels as $key => $lvl)
			if ($key == $userProfile) return true;
			
		return false;
	}
	
	private static function encodeWidgetListResponse(array $widgetList, $format = 'json')
	{
		$format = strtolower($format);
		if ($format != 'json' && $format != 'xml' && $format != 'raw')
			throw new BadArgumentException(MwwException::MODEL, 'Trying to encode widgetList result with an unapropriate format when only json, xml or raw are supported');
			
		switch ($format)
		{
			case 'json':
				
				return json_encode($widgetList);
				break;

			case 'raw':
				
				// No special treatment required.
				return $widgetList;
				break;
				
			case 'xml':
			default:
				
				header('Content-Type: text/xml');
				$dom = new DOMDocument('1.0', 'iso-8859-1');
				$root = $dom->createElement('widgets');
				foreach($widgetList as $widget)
				{
					$widgetNode = $dom->createElement('widget');
					$widgetNode->setAttribute('id', $widget['id']);
					$widgetNode->setAttribute('name', $widget['name']);
					$root->appendChild($widgetNode);
				}
				$dom->appendChild($root);
				
				return $dom->saveXML();
				break;
				
			
		}
	}
	
	private static function editRemoteWidget($widgetId, $uploadedFilePath)
	{	
		// -- XML manifed manipulation.
		// parse the manifest.
		$doc = new DOMDocument();
		$doc->preserveWhiteSpace = false;
		if(!@$doc->load($uploadedFilePath))
			throw new XMLParsingException(MwwException::MODEL, $uploadedFilePath);
		
		// validate the manifest.
		if (!@$doc->schemaValidate('./schema/manifest.xsd'))
			throw new XMLValidationException(MwwException::MODEL, $uploadedFilePath);

		// test if the id in the manifest is the same as the one already in the database.
		$id = $doc->getElementsByTagName('widget')->item(0)->getAttribute('id');
		
		// getting name/title of the widget.
		if ($doc->getElementsByTagName('name')->length)
			$name = $doc->getElementsByTagName('name')->item(0)->nodeValue;
		else if ($doc->getElementsByTagName('title')->length)
			$name = $doc->getElementsByTagName('title')->item(0)->nodeValue;
		else
			throw new XMLValidationException(MwwException::MODEL, "The XML File is not valid. The mandatory title or name element is missing.");
		
		// getting widget description.
		$description = null;
		if ($doc->getElementsByTagName('description')->length)
			$description = $doc->getElementsByTagName('description')->item(0)->nodeValue;
		
		if($id != $widgetId)
			throw new FileException(MwwException::MODEL, "The manifest id '$id' does not match the widget id '${widgetId}'");
		
		// test if the folder exists already (why wouldn't it ... but still).
		if (!file_exists('./widgets/' . $id))
			throw new FileException(MwwException::MODEL, "The widget directory for the widget '$id' doesn't exist.");

		// read the new preferences defined in the new manifest.
		$prefs = $doc->getElementsByTagNameNS('palette', 'preference');
		$prefsName = array();
		foreach($prefs as $pref)
			$prefsName[] = $pref->attributes->getNamedItem('name')->value;
		
		// read the old preferences defined in the old manifest.
		$docOld = new DOMDocument();
		$docOld->preserveWhiteSpace = false;
		if(!@$docOld->load('./widgets/' . $id . '/config.xml'))
			throw new FileException(MwwException::MODEL, "Unable to load the old manifest XML file for widget '${id}'");
			
		$prefsOld =  $docOld->getElementsByTagNameNS('palette', 'preference');
		$prefsOldName = array();
		foreach($prefsOld as $pref)
			$prefsOldName[] = $pref->attributes->getNamedItem('name')->value;
			
		$nameToReturn = $name;	
		
		// -- End of XML manifest manipulation.
		
		// -- Persistant data access section.
		// Database connexion and arguments escaping.
		$db = DbUtil::accessFactory();
		$id = $db->escape($id);
		$name = $db->escape($name);
		$description = $db->escape($description);
			
		// delete the old preferences values from the interface_preferences table.
		foreach($prefsOldName as $pref)
		{
			if(!in_array($pref, $prefsName))
			{
				$pref = $db->escape($pref);
				
				if (!$db->execute("DELETE FROM `interface_preferences` WHERE `widgetid`= '${id}' AND `preference` = '${pref}'"))
					throw new DBException(MwwException::MODEL, "Unable to delete preference '${pref}' for widget '${id}' in persistant data");
			}
		}
		
		// We update name (title element in widget spec) and description (which is nullable).
		if (!$db->execute("UPDATE `widgets` SET `widgetname` = '${name}', `description` = '${description}' WHERE `widgetid` = '${id}'"))
			throw new DBException(MwwException::MODEL, "Unable to update name and description for widget '${id}'");
		
		// -- End of Persistant data access section.

		// delete the old manifest.
		if (!@unlink('./widgets/' . $id . '/config.xml'))
			throw new FileException(MwwException::MODEL, 'Unable to delete the old manifest file.');

		// move the manifest to its directory.
		if (!@move_uploaded_file($uploadedFilePath, './widgets/' . $id . '/config.xml'))
			throw new FileException(MwwException::MODEL, 'The manifest could not be copied to its new directory.');
		
		// Everything was ok.
		return $nameToReturn;
	}
	
	private static function editLocalWidget($widgetId, $uploadedFilePath)
	{
	
		try
		{
			$unziper = new fileUnzip($uploadedFilePath);
			$archiveContent = $unziper->getFilesList();
			$tempFolder = './widgets/' . mktime();
			
			if (!self::isFileInArchive($archiveContent, 'config.xml'))
			{
				throw new FileException(MwwException::MODEL, 'Unable to find config.xml in the provided archive');
			}
			else if (!self::isFileInArchive($archiveContent, 'index.html') && !self::isFileInArchive($archiveContent, 'index.xul'))
			{
				throw new FileException(MwwException::MODEL, 'Unable to find index.html in the provided archive');
			}
			else
			{
				// The mandatory files are in the archive.
				// We unzip the document in the widget repository under an arbitrary folder.
				
				$unziper->unzipAll($tempFolder);
				$unziper->close();
				
				// parse the manifest.
				$manifestPath = $tempFolder . '/config.xml';
				
				// parse the manifest.
				$doc = new DOMDocument();
				$doc->preserveWhiteSpace = false;
				if(!@$doc->load($manifestPath))
					throw new XMLParsingException(MwwException::MODEL, $manifestPath);
					
				// validate the manifest.
				if (!@$doc->schemaValidate('./schema/manifest.xsd'))
					throw new XMLValidationException(MwwException::MODEL, $manifestPath);
					
				// test if the id in the manifest is the same as the one already in the database.
				$id = $doc->getElementsByTagName('widget')->item(0)->getAttribute('id');
				
				// getting name/title of the widget.
				if ($doc->getElementsByTagName('name')->length)
					$name = $doc->getElementsByTagName('name')->item(0)->nodeValue;
				else if ($doc->getElementsByTagName('title')->length)
					$name = $doc->getElementsByTagName('title')->item(0)->nodeValue;
				else
					throw new XMLValidationException(MwwException::MODEL, "The XML File is not valid. The mandatory title or name element is missing.");
				
				// getting widget description.
				$description = null;
				if ($doc->getElementsByTagName('description')->length)
					$description = $doc->getElementsByTagName('description')->item(0)->nodeValue;
				
				// setting target folder.
				$targetFolder = './widgets/' . $id;
				
				if($id != $widgetId)
					throw new BadArgumentException(MwwException::MODEL, "The manifest id '$id' does not match the widget id '".$widgetId."'");
				
				// test if the folder exists already (why wouldn't it ... but still).
				if (!file_exists($targetFolder))
					throw new BadArgumentException(MwwException::MODEL, "The widget directory for the id '$id' doesn't exist.");
		
				// read the new preferences defined in the new manifest.
				$prefs = $doc->getElementsByTagNameNS('palette', 'preference');
				$prefsName = array();
				foreach($prefs as $pref)
					$prefsName[] = $pref->attributes->getNamedItem('name')->value;
				
				// read the old preferences defined in the old manifest.
				$docOld = new DOMDocument();
				$docOld->preserveWhiteSpace = false;
				if(!@$docOld->load('./widgets/' . $id . '/config.xml'))
					throw new XMLParsingException(MwwException::MODEL, './widgets/' . $id . '/config.xml');
					
				$prefsOld =  $docOld->getElementsByTagNameNS('palette', 'preference');
				$prefsOldName = array();
				foreach($prefsOld as $pref)
					$prefsOldName[] = $pref->attributes->getNamedItem('name')->value;
				
				// This method must return the name of the widget, so we keep alive
				// a non-altered version of it.
				$nameToReturn = $name;
					
				// -- Persistant data access section.
				// database connexion retrieval and data escaping.
				$db 			= DbUtil::accessFactory();
				$widgetId 		= $db->escape($widgetId);
				$name 			= $db->escape($name);
				$description 	= $db->escape($description);
					
				// delete the old preferences values from the interface_preferences table.
				foreach($prefsOldName as $pref)
				{
					if(!in_array($pref, $prefsName))
					{
						$pref = $db->escape($pref);
						
						if (!$db->execute("DELETE FROM `interface_preferences` WHERE `widgetid`= '${id}' AND `preference` = '${pref}'"))
							throw new DBException(MwwException::MODEL, "Unable to delete preference '${pref}' for widget '${id}'");
					}
				}
				
				// We update name (title element in widget spec) and description (which is nullable).
				if (!$db->execute("UPDATE `widgets` SET `widgetname` = '${name}', `description` = '${description}' WHERE `widgetid` = '${id}'"))
					throw new DBException(MwwException::MODEL, "Unable to update widget name and description for widget '${id}'");
				
				// -- End of Persistant data access section.
					
				// The old widget folder must be deleted.
				@util::full_rmdir($targetFolder);
				
				// create the widget directory (actually we simply rename the temporary directory).
				if (!@rename($tempFolder, $targetFolder))
					throw new FileException(MwwException::MODEL, 'The widget directory could not be created');
				
				// Everything was ok.
				return $nameToReturn;
			}	
		}
		catch (Exception $e)
		{
			echo $e;
			if (isset($tempFolder) && is_dir($tempFolder))
				@Util::full_rmdir($tempFolder);
				
			throw $e;
		}
	}
	
	private static function installRemoteWidget($categoryId, $userId, $uploadedFilePath)
	{
		// -- XML Manifest manipulation section.
		// parse the manifest.
		$doc = new DOMDocument();
		$doc->preserveWhiteSpace = false;
		if(!@$doc->load($uploadedFilePath))
			throw new XMLValidationException(MwwException::MODEL, $uploadedFilePath);
		
		// validate the manifest.
		if (!@$doc->schemaValidate('./schema/manifest.xsd'))
			throw new XMLValidationException(MwwException::MODEL, $uploadedFilePath);

		// test if the id is unique (test if the directory exists).
		$id = $doc->getElementsByTagName('widget')->item(0)->getAttribute('id');
		
		// get the name/title of the widget.
		if ($doc->getElementsByTagName('name')->length)
			$name = $doc->getElementsByTagName('name')->item(0)->nodeValue;
		else if ($doc->getElementsByTagName('title')->length)
			$name = $doc->getElementsByTagName('title')->item(0)->nodeValue;
		else
			throw new XMLValidationException(MwwException::MODEL, "The XML File is not valid. The mandatory title or name element is missing.");
		
		// getting widget description.
		$description = null;
		if ($doc->getElementsByTagName('description')->length)
			$description = $doc->getElementsByTagName('description')->item(0)->nodeValue;
			
		// -- End of XML Manifest manipulation section.
		
		// -- File system section.
		if (file_exists('./widgets/' . $id))
			throw new FileException(MwwException::MODEL ,'A widget with the id \''.$id.'\' already exists.');

		// create the widget directory.
		if (!@mkdir('./widgets/' . $id))
			throw new FileException(MwwException::MODEL ,'The widget directory could not be created');
		
		// move the manifest to its directory.
		if (!@move_uploaded_file($uploadedFilePath, './widgets/' . $id . '/config.xml'))
		{
			@rmdir('./widgets/' . $id);
			throw new FileException(MwwException::MODEL ,'The manifest could not be copied to its new directory.');
		}
		
		$nameToReturn = $name;
		
		// -- End of file sytem section.
		
		// -- Database access section.
		// database connexion retrieval and data escaping.
		$db = DbUtil::accessFactory();
		$categoryId 	= ($categoryId != 0) ? $db->escape($categoryId) : 'NULL';
		$userId 		= $db->escape($userId);
		$name 			= $db->escape($name);
		$description 	= $db->escape($description);
		
		// insert the widget into the database.
		if (!$db->execute("INSERT INTO `widgets` (`widgetid`, `widgetname`, `visible`, `copname`, `category`, `description`) VALUES ('${id}', '${name}', 0, (SELECT `copname` FROM `users` WHERE `id`= ${userId}), ${categoryId}, '${description}')"))
		{
			@rmdir('./widgets/' . $id);
			throw new DBException(MwwException::MODEL, "Unable to insert the new widget '${id}' information in persistant data");
		}
	
		return $nameToReturn;
	}
	
	private static function installLocalWidget($categoryId, $userId, $uploadedFilePath)
	{

		try
		{
			$unziper = new fileUnzip($uploadedFilePath);
			$archiveContent = $unziper->getFilesList();
			$tempFolder = './widgets/' . mktime();
			$alreadyInstalled = false;
			
			if (!self::isFileInArchive($archiveContent, 'config.xml'))
			{
				throw new FileException(MwwException::MODEL, 'Unable to find config.xml in the provided archive');
			}
			else if (!self::isFileInArchive($archiveContent, 'index.html') && !self::isFileInArchive($archiveContent, 'index.xul'))
			{
				throw new FileException(MwwException::MODEL, 'Unable to find index.html in the provided archive');
			}
			else
			{
				// The mandatory files are in the archive.
				// We unzip the document in the widget repository under an arbitrary folder.
				
				$unziper->unzipAll($tempFolder);
				$unziper->close();
				
				// parse the manifest.
				$manifestPath = $tempFolder . '/config.xml';
				
				$doc = new DOMDocument();
				$doc->preserveWhiteSpace = false;
				if(!@$doc->load($manifestPath))
					throw new XMLParsingException(MwwException::MODEL, 'Unable to parse XML file. Document not well formed.');
				
				// validate the manifest.
				if (!@$doc->schemaValidate('./schema/manifest.xsd'))
					throw new XMLValidationException(MwwException::MODEL, "The XML File isn't valid according to the XML Schema.");
		
				// test if the id is unique (test if the directory exists).
				$id = $doc->getElementsByTagName('widget')->item(0)->getAttribute('id');
				
				// getting name/title of the widget.
				if ($doc->getElementsByTagName('name')->length)
					$name = $doc->getElementsByTagName('name')->item(0)->nodeValue;
				else if ($doc->getElementsByTagName('title')->length)
					$name = $doc->getElementsByTagName('title')->item(0)->nodeValue;
				else
					throw new XMLValidationException(MwwException::MODEL, "The XML File is not valid. The mandatory title or name element is missing.");
				
				// getting widget description.
				$description = null;
				if ($doc->getElementsByTagName('description')->length)
					$description = $doc->getElementsByTagName('description')->item(0)->nodeValue;
					
				// is widget authentication enabled or disabled ?
				$widgetAuth = false;
				$authKey = null;
				if ($doc->getElementsByTagName('widget_authentication')->length)
				{
					$authValue = $doc->getElementsByTagName('widget_authentication')->item(0)->nodeValue;
					
					if ($authValue == 'enabled')
					{
						$widgetAuth = true;
						
						// create the key.
						$crypto = new Rijndael();
						$authKey = $crypto->generateKey();
						
					}
				}
				
				$targetFolder = './widgets/' . $id;
				
				if (file_exists($targetFolder))
				{
					$alreadyInstalled = true;
					throw new FileException(MwwException::MODEL, 'A widget with the id \''.$id.'\' already exists.');
				}
				
				// create the widget directory (actually we simply rename the temporary directory).
				if (!@rename($tempFolder, $targetFolder))
					throw new FileException(MwwException::MODEL, 'The widget directory could not be created');
					
				$nameToReturn = $name;
				
				// -- Persistant data access section.
				$db 			= DbUtil::accessFactory();
				$categoryId 	= ($categoryId != 0) ? $db->escape($categoryId) : 'NULL';
				$userId 		= $db->escape($userId);
				$name			= $db->escape($name);
				$description 	= $db->escape($description);
				$generatedKey	= ($widgetAuth) ? "'${authKey}'" : 'NULL';
				
				// insert the widget into the database.
				if (!$db->execute("INSERT INTO `widgets` (`widgetid`, `widgetname`, `visible`, `copname`, `category`, `description`, `authkey`) VALUES ('${id}', '${name}', 0, (SELECT `copname` FROM `users` WHERE `id`= ${userId}), ${categoryId}, '${description}', ${generatedKey})"))
					throw new DBException(MwwException::MODEL, "Unable to insert new widget '${id}' information in persistant data");
					
				// -- End of Persistant data access section.

				// End of the installation. Everything was fine.
				return $nameToReturn;
			}
		}
		catch(Exception $ex)
		{
			if (isset($tempFolder) && is_dir($tempFolder))
				Util::full_rmdir($tempFolder);
				
			if (isset($targetFolder) && is_dir($targetFolder) && !$alreadyInstalled)
				Util::full_rmdir($targetFolder);

			throw $ex;
		}	
	}
	
	private static function isFileInArchive(array $files, $search)
	{
		foreach ($files as $file)
		{
			if ($file == $search)
				return true;
		}

		return false;
	}
}
?>